/*
 * NetConnection
 *
 * Author: Hanno Braun
 */

package sv.net2;



import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;



/**
 * Handles the network connection. Can work in server and client mode.
 * After being constructed, this class must be run by a thread to be able to receiver messages.
 */

public class NetConnection implements Runnable {

	private static final byte READY = 2;
	private static final byte SHOOT = 3;
	private static final byte HIT_WATER = 4;
	private static final byte HIT_SHIP = 5;
	private static final byte SHIP_DESTROYED = 6;
	private static final byte I_AM_LOSER = 7;
	
	// The length of each command.
	private byte[] length = {-1, -1, 1, 2, 2, 2, 2, 1};

	private SocketChannel socket;
	private Receiver receiver;
	
	private ByteBuffer outgoing;
	
	private Thread thread;
	
	
	
	/**
	 * Initializes the network connection.
	 * If in server mode, this method blocks until a client has connected.
	 *
	 * @param server True if the connection should act as a server, false otherwise.
	 * @param address If in server mode, specifies the port the connection listens on, otherweise specifies
	 *                the complete remote address.
	 * @param aReceiver The object responsible for processing network messages.
	 *
	 * @throws IOException if an I/O error occurs.
	 */
	
	public NetConnection(boolean server, InetSocketAddress address, Receiver aReceiver) {
		try {
			if (server) {
				ServerSocketChannel serverSocket = ServerSocketChannel.open();
				serverSocket.socket().bind(address);
				socket = serverSocket.accept();
			}
			else {
				socket = SocketChannel.open();
				socket.connect(address);
			}
			
			receiver = aReceiver;
			
			outgoing = ByteBuffer.allocate(100);
		}
		catch (IOException e) {
			receiver.criticalError(e);
		}
	}
	
	
	
	/**
	 * Receives network message and passes them on to the receiver.
	 */
	
	public void run() {
		thread = Thread.currentThread();
		ByteBuffer incoming = ByteBuffer.allocate(100);
		byte field;
		
		try {
			while (!Thread.currentThread().isInterrupted()) {
				socket.read(incoming);
				incoming.flip();

				while (incoming.position() < incoming.limit()) {
					incoming.mark();

					byte message = incoming.get();

					try {
						if (message == READY) {
							receiver.ready();
						}
						else if (message == SHOOT) {
							field = incoming.get();
							receiver.shoot(field);
						}
						else if (message == HIT_WATER) {
							field = incoming.get();
							receiver.hitWater(field);
						}
						else if (message == HIT_SHIP) {
							field = incoming.get();
							receiver.hitShip(field);
						}
						else if (message == SHIP_DESTROYED) {
							field = incoming.get();
							receiver.destroyedShip(field);
						}
						else if (message == I_AM_LOSER) {
							receiver.lostGame();
						}
						else {
							Exception e = new Exception() {
								
								public String toString() {
									return "Received unknown message from opponent.";
								}
							};
							
							receiver.criticalError(e);
						}
					}
					catch (BufferUnderflowException e) {
						incoming.reset();
						break;
					}
				}
				
				incoming.compact();
			}
		}
		catch (IOException e) {
			receiver.criticalError(e);
		}
		
		try {
			socket.close();
		}
		catch (IOException e) {
			receiver.criticalError(e);
		}
	}
	
	
	
	/**
	 * Shuts down the operation of this object and closes its connection.
	 */
	
	public void shutDown() {
		thread.interrupt();
	}
	
	
	
	/**
	 * Sends the contents of the outgoing buffer.
	 */
	
	private void send() {
		try {
			outgoing.flip();
			socket.write(outgoing);
			outgoing.compact();
		}
		catch (IOException e) {
			receiver.criticalError(e);
		}
	}
	
	
	
	/**
	 * Sends the ready message.
	 */
	
	public synchronized void sendReady() {
		outgoing.put(READY);
		send();
		
		System.out.println("SENT: ready ()");
	}
	
	
	
	/**
	 * Sends the shoot message.
	 */
	
	public synchronized void sendShoot(byte field) {
		outgoing.put(SHOOT);
		outgoing.put(field);
		send();
		
		System.out.println("SENT: shoot (" + field + ")");
	}
	
	
	
	/**
	 * Sends the hit_water message.
	 */
	
	public synchronized void sendHitWater(byte field) {
		outgoing.put(HIT_WATER);
		outgoing.put(field);
		send();
		
		System.out.println("SENT: hit_water (" + field + ")");
	}
	
	
	
	/**
	 * Sends the hit_ship message.
	 */
	
	public synchronized void sendHitShip(byte field) {
		outgoing.put(HIT_SHIP);
		outgoing.put(field);
		send();
		
		System.out.println("SENT: hit_ship (" + field + ")");
	}
	
	
	
	/**
	 * Sends the ship_destroyed message.
	 */
	
	public synchronized void sendShipDestroyed(byte field) {
		outgoing.put(SHIP_DESTROYED);
		outgoing.put(field);
		send();
		
		System.out.println("SENT: destroyed_ship (" + field + ")");
	}
	
	
	
	/**
	 * Sends the i_am_loser message.
	 */
	
	public synchronized void sendIAmLoser() {
		outgoing.put(I_AM_LOSER);
		send();
		
		System.out.println("SENT: i_am_loser ()");
	}
}
